package com.gitee.simpleplus.nesty.proxy;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.simpleplus.nesty.commons.annotations.ExecChainMethod;
import com.gitee.simpleplus.utils.ScanClassUtil;

import javassist.*;
import static org.apache.commons.lang3.CharUtils.LF;

public class ExecChainProxy {
	
	private static final Logger log = LoggerFactory.getLogger(ExecChainProxy.class);

	private static final String APPEND_METHOD_NAME = "_appendEcProxyEntity";
	
	public static final char SP = ' ';//空格


	public static <T> T genExecChainObj(Class<T> interCls, String pack) throws Exception {
		return genExecChainObj(interCls, ScanClassUtil.scan0(pack));
	}
	
	public static <T> T genExecChainObj(Class<T> interCls, Set<Class<?>> classes) throws Exception {
		Class<T> clazz = generateExecChainCls(interCls);
		T t = clazz.newInstance();
		Method method = clazz.getMethod(APPEND_METHOD_NAME, interCls);
		classes.stream().forEach(clazzTmp -> {
			if(clazzTmp.isInterface() || clazzTmp.isSynthetic() || clazzTmp.isAnonymousClass() || clazzTmp.isEnum() || clazzTmp.isAnnotation()){
				return ;
			}
			if(interCls.isAssignableFrom(clazzTmp)){
				try {
					method.invoke(t, clazzTmp.newInstance());
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}

		});
		return t;
	}

	private static <T> Class<T> generateExecChainCls(Class<T> interCls) throws Exception {
		if (!interCls.isInterface()) {
			throw new RuntimeException(String.format("%s is not interface!", interCls.getName()));
		}
		ClassPool cp = ClassPool.getDefault();
		CtClass ec_proxy = cp.makeClass(interCls.getName() + "$$EC_proxy");
		CtClass interCtClass = cp.getCtClass(interCls.getName());
		ec_proxy.addInterface(interCtClass);

		ec_proxy.addField(CtField.make("java.util.LinkedList _execChains = new java.util.LinkedList();", ec_proxy));

		CtMethod append = CtMethod.make("public void " + APPEND_METHOD_NAME + "(" + interCls.getName() + " entity){}", ec_proxy);
		append.insertBefore("this._execChains.add($1);");
		ec_proxy.addMethod(append);

		CtMethod remove = CtMethod.make("public void _removeEcProxyEntity(" + interCls.getName() + " entity){}", ec_proxy);
		remove.insertBefore("this._execChains.remove($1);");
		ec_proxy.addMethod(remove);

		Arrays.stream(interCtClass.getDeclaredMethods()).forEach(m -> {

			try {
				StringBuffer sb = new StringBuffer("{")
						.append(LF).append(SP).append("for(java.util.Iterator it = _execChains.iterator(); it.hasNext();){")
						.append(LF).append(SP).append(SP).append(interCls.getName()).append(SP).append(SP).append("_").append(interCls.getSimpleName()).append("_ec = (").append(interCls.getName()).append(")it.next();")
						.append(LF).append(SP).append(SP);
				if(m.getReturnType() == CtClass.voidType){
					sb.append("_").append(interCls.getSimpleName()).append("_ec.").append(appendMethodSrc(m))
					.append(LF).append(SP).append(SP).append("}");
				}else if(m.getReturnType() == CtClass.booleanType){
					ExecChainMethod execChainMethod =  (ExecChainMethod) m.getAnnotation(ExecChainMethod.class);
					if(execChainMethod == null){
						throw new RuntimeException("execChain is null, but we need it...");
					}
					boolean ifTrueReturn = execChainMethod.ifTrueReturn();
					sb.append("boolean _ec_result =  _").append(interCls.getSimpleName()).append("_ec.").append(appendMethodSrc(m))
					.append(LF).append(SP).append(SP).append("if(_ec_result == " ).append(ifTrueReturn).append("){")
					.append(LF).append(SP).append(SP).append(SP).append("return _ec_result;")
					.append(LF).append(SP).append(SP).append(SP).append("}")
					.append(LF).append(SP).append("}")
					.append(LF).append(SP).append("return ").append(!ifTrueReturn).append(";");
				}else {
					sb.append("Object _ec_result =  _").append(interCls.getSimpleName()).append("_ec.").append(appendMethodSrc(m))
					.append(LF).append(SP).append(SP).append("if(_ec_result != null){")
					.append(LF).append(SP).append(SP).append(SP).append("return _ec_result;")
					.append(LF).append(SP).append(SP).append("}")
					.append(LF).append(SP).append("}")
					.append(LF).append(SP).append("return null;");
				}
				sb.append(LF).append("}");
				log.info(sb.toString());;
				ec_proxy.addMethod(CtNewMethod.make(m.getReturnType(), m.getName(), m.getParameterTypes(), m.getExceptionTypes(), sb.toString(), ec_proxy));

			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		});

		return ec_proxy.toClass();
	}
	
	private static String appendMethodSrc(CtMethod m) throws Exception{
		StringBuffer args = new StringBuffer(m.getName()).append("(");
		for (int i = 0; i < m.getParameterTypes().length; i++) {
			if (i == 0) {
				args.append("$").append(i + 1);
			} else {
				args.append(",").append(SP).append("$").append(i + 1);
			}
		}
		args.append(");");
		return args.toString();
	}
}